/* sockets.c */

/************************************************************************************
*                    Copyright Aquantia Corporation
*                              Freeware
*
* Description:
*
*   This file implements socket abstraction functions for POSIX and WinSock.
*
************************************************************************************/

/*! \file
    This file implements socket abstraction functions for POSIX and WinSock.
*/

#include "sockets.h"
#ifdef USE_WINSOCK
  #include <Mstcpip.h>
#else
  #include <string.h>
  #include <errno.h>
  #include <sys/utsname.h>        /* ANSI uses uname(), whereas Windows uses gethostname() */
#endif


#ifdef __cplusplus
  extern "C" {
#endif

/*! \addtogroup Sockets Socket Library
  @{*/

#ifdef USE_WINSOCK
#define ERR_STR_SIZE  256
static char errStr[ERR_STR_SIZE];
#endif

/* Function initiates use of socket library if necessary. */
int sockInitialize()
{
#ifdef USE_WINSOCK
  WORD wVersionRequested = MAKEWORD(2, 2);
  WSADATA wsaData;
  int err;
  
  /* An application must call the WSACleanup() function for every successful time 
     the WSAStartup() function is called. This means, for example, that if 
     an application calls WSAStartup() three times, it must call WSACleanup() three times. 
     The first two calls to WSACleanup() do nothing except decrement an internal counter; 
     the final WSACleanup() call for the task does all necessary resource deallocation for the task. */

  if ((err = WSAStartup (wVersionRequested, &wsaData)) != 0)
  {
    /* We could not find a usable Winsock DLL. */
    /* Set error explicitly to get error by common GetLastError(). */
    WSASetLastError (err);
    return err;
  }
  
  /* Confirm that the WinSock DLL supports 2.2.*/
  if (LOBYTE (wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
  {
    WSACleanup ();
    /* Tell the user that we could not find a usable WinSock DLL. */
    WSASetLastError (WSAVERNOTSUPPORTED);
    return WSAVERNOTSUPPORTED;
  }
  return 0;
#else
  return 0;
#endif
}

/* Function terminates use of socket library if necessary. */
int sockClose()
{
#ifdef USE_WINSOCK
  /* Only the final WSACleanup() function call performs the actual cleanup. 
     The preceding calls simply decrement an internal reference count */
  return WSACleanup();
#else
  return 0;
#endif
}

/* Returns error string for the last sockets operation that failed */
char* get_socket_error()
{
#ifdef USE_WINSOCK
  FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 
                NULL, WSAGetLastError(),
                /* MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), */
                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                (LPTSTR)&errStr, 
                ERR_STR_SIZE, NULL);
  return errStr;
#else
  return strerror (errno);
#endif
}

/* Compares last socket error with specified POSIX socket error code */
int is_socket_error_equal(int code)
{
#ifdef USE_WINSOCK
  return GetLastError() == WSABASEERR + code;
#else
  return errno == code;
#endif
}

/* Retrieves the standard host name for the local computer */
int get_host_name(char* name, int namelen)
{
#ifdef USE_WINSOCK
  /* Windows does not support the ANSI uname() call */
  return gethostname (name, namelen);
#else
  struct utsname unameBuffer;
  int err;
  if ((err = uname (&unameBuffer)) == 0)
    strncpy (name, unameBuffer.nodename, namelen);
  return err;
#endif
}

#ifdef USE_WINSOCK
/* This function has to been defined locally as this was not implemented in Windows until Vista */
int Windows_inet_pton (int af, const char *src, void *dst)
{
  struct sockaddr_storage ss;
  int size = sizeof(ss);
  char src_copy[INET6_ADDRSTRLEN+1];

  ZeroMemory(&ss, sizeof(ss));
  /* stupid non-const API */
  strncpy (src_copy, src, INET6_ADDRSTRLEN+1);
  src_copy[INET6_ADDRSTRLEN] = 0;

  if (WSAStringToAddress(src_copy, af, NULL, (struct sockaddr *)&ss, &size) == 0)
  {
    switch(af) 
    {
    case AF_INET:
      *(struct in_addr *)dst = ((struct sockaddr_in *)&ss)->sin_addr;
      return 1;
    case AF_INET6:
      *(struct in6_addr *)dst = ((struct sockaddr_in6 *)&ss)->sin6_addr;
      return 1;
    }
  }
  return 0;
}
#endif


/*! Function sets specified TCP keep-alive options. 
    Non-zero return value indicates failure during setting. 
    Warning can be printed to verbose string. */
int setTcpKeepAliveOptions(SOCKET socket, int time, int timeSpecified,
                           int interval, int intervalSpecified, int count, int countSpecified,
                           char* verbose)
{
#ifdef USE_WINSOCK
  if (((timeSpecified) && (!intervalSpecified)) ||
      ((!timeSpecified) && (intervalSpecified)))
    strcat(verbose, "TCP keep-alive idle and interval should be defined both\n");
  if (countSpecified)
    strcat(verbose, "TCP keep-alive attemts can't be specified\n");
  if ((timeSpecified) && (intervalSpecified))
  {
    struct tcp_keepalive settings;
    DWORD bytesReturned;
    
    settings.onoff = 1;
    settings.keepalivetime = time * 1000;
    settings.keepaliveinterval = interval * 1000;

    if (WSAIoctl(socket, SIO_KEEPALIVE_VALS, &settings, sizeof(settings),
                 NULL, 0, &bytesReturned, NULL, NULL) != 0)
    {
      return TCP_SERVER_E_SOCKET_SET_OPT;
    }
  }
  return 0;
#else
  if ((timeSpecified) &&
      (setsockopt (socket, IPPROTO_TCP, TCP_KEEPIDLE, (const void*) &time, sizeof (time))))
  {
    return TCP_SERVER_E_SOCKET_SET_OPT;
  }
  if ((intervalSpecified) &&
      (setsockopt (socket, IPPROTO_TCP, TCP_KEEPINTVL, (const void*) &interval, sizeof (interval))))
  {
    return TCP_SERVER_E_SOCKET_SET_OPT;
  }
  if ((countSpecified) &&
      (setsockopt (socket, IPPROTO_TCP, TCP_KEEPCNT, (const void*) &count, sizeof (count))))
  {
    return TCP_SERVER_E_SOCKET_SET_OPT;
  }
  return 0;
#endif
}

/*! Function reads TCP keep-alive options.
   Non-zero return value indicates failure during reading. */
int getTcpKeepAliveOptions(SOCKET socket, int* time, int* timeRead,
                           int* interval, int* intervalRead, int* count, int* countRead)
{
#ifdef USE_WINSOCK
  *timeRead = 0;
  *intervalRead = 0;
  *countRead = 0;
  return 0;
#else
  socklen_t optionValueLength;
  *timeRead = 0;
  *intervalRead = 0;
  *countRead = 0;
  
  optionValueLength = sizeof(int);
  if (getsockopt (socket, IPPROTO_TCP, TCP_KEEPIDLE, (void*) time, &optionValueLength) == 0)
  {
    *timeRead = 1;
  }
  optionValueLength = sizeof(int);
  if (getsockopt (socket, IPPROTO_TCP, TCP_KEEPINTVL, (void*) interval, &optionValueLength) == 0)
  {
    *intervalRead = 1;
  }
  optionValueLength = sizeof(int);
  if (getsockopt (socket, IPPROTO_TCP, TCP_KEEPCNT, (void*) count, &optionValueLength) == 0)
  {
    *countRead = 1;
  }
  return ((timeRead) && (intervalRead) && (countRead)) ? 0 : TCP_SERVER_E_SOCKET_GET_OPT;
#endif
}

/*@}*/

#ifdef __cplusplus
}
#endif
